using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace PacketScreensaver.Net.PacketMonitor
{
    /// <summary>
    /// Represents the method that will handle the NewPacket event.
    /// </summary>
    /// <param name="pm">The <see cref="Monitor"/> that intercepted the <see cref="Packet"/>.</param>
    /// <param name="p">The newly arrived <see cref="Packet"/>.</param>
    public delegate void NewPacketEventHandler(Monitor monitor, Packet packet);

    /// <summary>
    /// Monitors packets on a specified interface
    /// </summary>
    public class Monitor
    {
        /// <summary>
        /// Holds all the listeners for the NewPacket event.
        /// </summary>
        public event NewPacketEventHandler NewPacket;

        private bool _isRunning;
        private Socket _socket;
        private IPAddress _ip;
        private byte[] _buffer;

        /// <summary>
        /// Returns the running status of the packet monitor
        /// </summary>
        public bool IsRunning 
        {
            get 
            { 
                return _isRunning; 
            }
        }

        /// <summary>
        /// The interface used to intercept IP packets.
        /// </summary>
        /// <value>An <see cref="IPAddress"/> instance.</value>
        public IPAddress IP
        {
            get
            {
                return _ip;
            }
        }

        /// <summary>
        /// The buffer used to store incoming IP packets.
        /// </summary>
        /// <value>An array of bytes.</value>
        protected byte[] Buffer
        {
            get
            {
                return _buffer;
            }
        }

        /// <summary>
        /// Initialized a new instance of the Monitor class.
        /// </summary>
        /// <param name="ip"></param>
        public Monitor(IPAddress ip)
        {
            _ip = ip;
            _buffer = new byte[65535];
            _isRunning = false;
        }

        /// <summary>
        /// Starts listening on the specified interface.
        /// </summary>
        public void Start()
        {
            if (_socket == null)
            {
                try
                {
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
                    _socket.Bind(new IPEndPoint(_ip, 0));
                    _socket.IOControl(IOControlCode.ReceiveAll, BitConverter.GetBytes((int)1), null);
                    _socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), null);
                    _isRunning = true;
                }
                catch (Exception ex)
                {
                    _socket = null;
                    Console.WriteLine(ex.ToString());
                }
            }
        }

        /// <summary>
        /// Stops listening on the specified interface.
        /// </summary>
        public void Stop()
        {
            if (_socket != null)
            {
                _socket.Close();
                _socket = null;
                _isRunning = false;
            }
        }

        /// <summary>
        /// Called when the socket intercepts a packet.
        /// </summary>
        /// <param name="ar">The asynchronous result.</param>
        private void OnReceive(IAsyncResult ar)
        {
            try
            {
                Socket listener = (Socket) ar.AsyncState;
                int received = _socket.EndReceive(ar);
                try
                {
                    if (_socket != null)
                    {
                        byte[] packetBytes = new byte[received];
                        Array.Copy(_buffer, 0, packetBytes, 0, received);
                        OnNewPacket(new Packet(packetBytes));
                    }
                }
                catch (Exception exception)
                {
                    // invalid packet; ignore
                    Console.WriteLine(exception.ToString());
                } 
                _socket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive), null);
            }
            catch
            {
                Stop();
            }
        }

        /// <summary>
        /// Raises an event that indicates a new packet has arrived.
        /// </summary>
        /// <param name="p">The arrived <see cref="Packet"/>.</param>
        private void OnNewPacket(Packet p)
        {
            if (NewPacket != null)
                NewPacket(this, p);
        }

    }
}
